<?php

class DbModel extends DbBase
{
    protected $dbhelper;
    protected $mapper;
	protected $container = array();
	
    public function __construct( IDbHelper $dbhelper, DatabaseMap $map )
    {
        $this->dbhelper = $dbhelper;
        $this->mapper = $map->bind();
        
        parent::__construct( $this->mapper->properties );
        
        unset( $this->mapper->properties );
    }
    
    public function fetch( $id )
    {
        if( $this->_getById( $id ) )
		{
			$this->set( $this->mapper->fieldMaps[ $this->mapper->primary ], $id );
		}
		
		return $this->properties;
    }
    
	public function fetchAll( $limit = 0 )
    {
        return $this->_getAll( $limit );		
    }
	
	public function fetchWithSelection( $selection, array $args = array(), $limit = 0 )
    {
        return $this->_getAllWithSelection( $selection, $args, $limit );		
    }
	
	public function fetchAllWithFlag( $id, $limit = 0 )
    {
        return $this->_getAllWithFlag( $id, $limit );		
    }
	
    public function insert()
    {
		$params = array();
	    foreach( $this->mapper->fields as $val )
	    {
		    $params[ $val ] = $this->get( $this->mapper->fieldMaps[ $val ] );			
	    }
		
	    if( $this->mapper->isString )
	    {
		    $params[ $this->mapper->primary ] = $this->get( $this->mapper->fieldMaps[ $this->mapper->primary ] );
	    }
	
	    try
	    {
		    $this->dbhelper->exec( $this->_getSqlSyntaxInsert(), $params );
			$lastId = $this->lastInsertId();
		    $this->set( $this->mapper->fieldMaps[ $this->mapper->primary ], $lastId );
			
			$sqlRelation = $this->_getSqlSyntaxInsertRelation();
			if( count( $sqlRelation ) > 0 )
			{
				foreach( $sqlRelation as $key => $sql )
				{
					$relations = $this->get( $key );
					if( count( $relations ) > 0 )
					{
						foreach( $relations as $val )
						{
							$params = array();
							$params[ $this->mapper->primeAlias ] = $lastId;
							$params[ $this->mapper->relations[ $key ][DbMapper::KEY_RELATION] ] = $val;
							
							$this->dbhelper->exec( $sql, $params );
						}
					}
				}
			}
	    }
	    catch( Exception $e )
	    {
		    return false;
	    }
	
	    return true;
    }

    public function update()
    {
        $params = array();
	    foreach( $this->mapper->fields as $val )
	    {
		    $params[ $val ] = $this->get( $this->mapper->fieldMaps[ $val ] );
	    }
	
	    $params[ $this->mapper->primary ] = $this->get( $this->mapper->fieldMaps[ $this->mapper->primary ] );
	
	    try
	    {
			$this->dbhelper->exec( $this->_getSqlSyntaxUpdate(), $params );
	    }
	    catch( Exception $e )
	    {
		    return false;
	    }
	
	    return true;
    }

    public function remove()
    {
        $params = array();
	    $params[ $this->mapper->primary ] = $this->get( $this->mapper->fieldMaps[ $this->mapper->primary ] );
	
	    try
	    {
		    $this->dbhelper->exec( $this->_getSqlSyntaxRemove(), $params );
	    }
	    catch( Exception $e )
	    {
		    return false;
	    }
	
	    return true;
    }
    
    public function lastInsertId()
    {
        try
	    {
		    $sql = sprintf( 'SELECT max( %s ) as last_insert FROM %s LIMIT 1', $this->mapper->primary, $this->mapper->table );
		    $result = $this->dbhelper->getAll( $sql, array() );
	    }
	    catch( Exception $e )
	    {
	    }
	
	    return ( $result === array() )? 1: $result[0]['last_insert'];
    }
    
    protected function _getSqlSyntaxRemove()
    {
        return sprintf( 
		    'DELETE FROM %s WHERE %s=:%s ', 
		    $this->mapper->table, $this->mapper->primary, $this->mapper->primary 
	    );		
    }

    protected function _getSqlSyntaxUpdate()
    {
        $fields = array();
	    foreach( $this->mapper->fields as $val )
	    {
		    $fields []= sprintf( '%s=:%s', $val, $val );
	    }
	
	    return sprintf( 
		    'UPDATE %s SET %s WHERE %s=:%s ', 
		    $this->mapper->table, implode( ', ', $fields ), $this->mapper->primary, $this->mapper->primary 
	    );		
    }

    protected function _getSqlSyntaxInsert()
    {
        $fields = array();
	    if( $this->mapper->isString )
	    {
		    $fields[0][] = $fields[1][] = $this->mapper->primary;
	    }
	
	    foreach( $this->mapper->fields as $val )
	    {
		    $fields[0][] = $val;
		    $fields[1][] = ':'.$val;
	    }
	
	    return sprintf( 
		    'INSERT INTO %s ( %s ) VALUES ( %s )', 
		    $this->mapper->table, implode( ', ', $fields[0] ), implode( ', ', $fields[1] ) 
	    );		
    }
    
	protected function _getSqlSyntaxInsertRelation()
    {
		$sql = array();
	    foreach( $this->mapper->relations as $key => $val )
	    {
			if( isset( $val[ DbMapper::KEY_IS_FIELD ] ) ) continue;
			$sql[ $key ] = sprintf( 
				'INSERT INTO %s ( %s, %s ) VALUES ( :%s, :%s )', 
					$val[ DbMapper::KEY_RTABLE ], 
					$this->mapper->primeAlias, 
					$val[ DbMapper::KEY_RELATION ], 
					$this->mapper->primeAlias, 
					$val[ DbMapper::KEY_RELATION ]
			);
		}
	
	    return $sql;
    }
	
	protected function _getAllWithFlag( $idFlag, $limit )
	{
		if( is_array( $idFlag ) )
		{
			$placeholder = '?';
			if( ( $max = count( $idFlag ) ) > 1 )
			{
				for( $i=1; $i<$max; ++$i )
				{
					$placeholder .= ',?';
				}
			}
			
			$sql = sprintf( 
				'SELECT *,\'1\' as flag FROM %s WHERE %s IN ( %s )', $this->mapper->table, $this->mapper->primary, $placeholder
			);
			
			$sql2 = sprintf( 
				'SELECT *,\'\' as flag FROM %s WHERE %s NOT IN ( %s )', $this->mapper->table, $this->mapper->primary, $placeholder
			);
		}
		else
		{
			$sql = sprintf( 
				'SELECT *,\'1\' as flag FROM %s WHERE %s=:%s', $this->mapper->table, $this->mapper->primary, $this->mapper->primary
			);
			
			$sql2 = sprintf( 
				'SELECT *,\'\' as flag FROM %s WHERE %s<>:%s', $this->mapper->table, $this->mapper->primary, $this->mapper->primary
			);
		}
		
		foreach( $this->mapper->relations as $id => $val )
		{
			if( isset( $val[ DbMapper::KEY_IS_FIELD ] ) AND $val[ DbMapper::KEY_IS_FIELD ] )
			{
				$sql = sprintf( '%s JOIN %s USING ( %s ) ', $sql, $val[ DbMapper::KEY_TABLE ], $val[ DbMapper::KEY_RELATION ] );
				$sql2 = sprintf( '%s JOIN %s USING ( %s ) ', $sql2, $val[ DbMapper::KEY_TABLE ], $val[ DbMapper::KEY_RELATION ] );
			}
		}
		
		$sql = sprintf( '%s UNION %s ', $sql, $sql2 );
		
		if( $limit > 0 )
		{
			$sql = sprintf( 
			    '%s LIMIT %d', $sql, $limit 
		    );
		}
		
		if( is_array( $idFlag ) )
		{
			return $this->dbhelper->getAll( $sql, array_merge( $idFlag, $idFlag ) );
		}
		
		return $this->dbhelper->getAll( $sql, array( $this->mapper->primary => $idFlag ) );
	}
	
	protected function _getAllWithSelection( $selection, array $args = array(), $limit )
	{
		$sql = sprintf( 'SELECT * FROM %s ', $this->mapper->table );
		foreach( $this->mapper->relations as $id => $val )
		{
			if( isset( $val[ DbMapper::KEY_IS_FIELD ] ) AND $val[ DbMapper::KEY_IS_FIELD ] )
			{
				$sql = sprintf( '%s JOIN %s USING ( %s ) ', $sql, $val[ DbMapper::KEY_TABLE ], $val[ DbMapper::KEY_RELATION ] );
			}
		}
		
		$sql = sprintf( '%s WHERE %s ', $sql, $selection );
		
		if( $limit > 0 )
		{
			$sql = sprintf( 
			    '%s LIMIT %d', $sql, $limit 
		    );
		}
		
		return $this->dbhelper->getAll( $sql, $args );
	}
	
	protected function _getAll( $limit )
	{
		$sql = sprintf( 
			'SELECT * FROM %s ', $this->mapper->table
		);
			
		
		foreach( $this->mapper->relations as $id => $val )
		{
			if( isset( $val[ DbMapper::KEY_IS_FIELD ] ) AND $val[ DbMapper::KEY_IS_FIELD ] )
			{
				$sql = sprintf( '%s JOIN %s USING ( %s ) ', $sql, $val[ DbMapper::KEY_TABLE ], $val[ DbMapper::KEY_RELATION ] );
			}
		}
		
		if( $limit > 0 )
		{
			$sql = sprintf( 
			    '%s LIMIT %d', $sql, $limit 
		    );
		}
				
		return $this->dbhelper->getAll( $sql, array() );
	}
	
	protected function _getById( $id )
    {
        try
	    {
		    $sql = sprintf( 
			    'SELECT * FROM %s WHERE %s=:%s LIMIT 1', 
			    $this->mapper->table, $this->mapper->primary, $this->mapper->primary 
		    );
		    
		    $result = $this->dbhelper->getAll( $sql, array( $this->mapper->primary => $id ) );
		    if( $result === array() )
		    {
			    return false;
		    }
		
		    foreach( $result[0] as $key => $val )
		    {
			    $this->properties[ $this->mapper->fieldMaps[ $key ] ] = $val;
		    }
			
			foreach( $this->mapper->relations as $key => $map )
			{
				if( isset( $map['rtable'] ) )
				{
					$sql = sprintf( 
						'SELECT * FROM %s a INNER JOIN %s b ON a.%s=b.%s WHERE %s=:%s ', 
						$map['rtable'], $map['table'], 
						$map['relation'], $map['rprimary'], 
						$this->mapper->primeAlias, $this->mapper->primary
					);
				}
				else
				{
					$sql = sprintf( 
						'SELECT * FROM %s WHERE %s IN ( SELECT %s FROM %s WHERE %s=:%s )', $map['table'], $map['relation'], 
						$map['relation'], $this->mapper->table, $this->mapper->primary, $this->mapper->primary
					);
				}
				
				$result = $this->dbhelper->getAll( $sql, array( $this->mapper->primary => $id ) );
				if( $result !== array() )
				{
					if( isset( $map['rtable'] ) )
					{
						$this->properties[ $key ] = $result;
					}
					else
					{
						$this->properties[ $key ] = $result[0];
					}
				}
			}
	    }
	    catch( Exception $e )
	    {
		    return false;
	    }
	
	    return true;
    }
}